package basis.ea;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.hamcrest.internal.ArrayIterator;
import basis.NichtImplementiertAusnahme;


/**
 * Diese Klasse dient dazu ein Objekt in verschiedenen Formaten 
 * (z.B. CSV, TSV, Text, HTML) zu exportieren. Diese Klasse ist in der 
 * Lage sowohl Objekte als auch Listen zu verarbeiten. 
 * 
 * Datei: Exportdatei.java                   
 * Datum: 10.12.2011   
 *                  
 * @author Alexander Bredo 
 * @version 0.2
 */
public class Exportdatei
{
  private Exportformate exportformat;
  private String ordnerpfad;
  private String dateiname;
  private ArrayList<Datensatz> datensatzListe = 
      new ArrayList<Datensatz> ();
  private String dateiBeginn;
  private String dateiEnde;
  private String datensatzBeginn;
  private String datensatzEnde;
  private String feldnameBeginn;
  private String feldnameEnde;
  private String kopfzeileBeginn;
  private String kopfzeileEnde;
  private String datenBeginn;
  private String datenEnde;
  private String feldBeginn;
  private String feldEnde;
  
  
  /**
   * Kopierkonstruktor fuer rekursiven Aufruf aus der Klasse Datenfeld
   */
  protected Exportdatei (Object objekt, Exportdatei ed) throws IOException 
  {
    this (objekt, ed.gibOrdnerpfad (), ed.gibExportformat ());
  }
  
  
  /**
   * Erstellt die Exportdatei und speichert sie im angegeben Ordner ab
   */
  public Exportdatei (Object objekt, String ordnerpfad, 
      Exportformate ef) throws IOException 
  {
    this.exportformat = ef;
    this.ordnerpfad = ordnerpfad;
    this.dateiname = objekt.getClass ().getSimpleName () + 
        "." + ef.toString ();
    
    switch (ef)
    {
      case CSV:
        initCSV ();
        break;
      case HTML:
        initHTML ();
        break;
      case TSV:
        initTSV ();
        break;
      case TXT:
        initText ();
        break;
      case PDF:
      default:
        throw new NichtImplementiertAusnahme (
            "Exportformat nicht implementiert");
    }
    
    objektEinlesen (objekt);
  }
  
  
  /**
   * Liest das Objekt ein und delegiert die Verarbeitung. 
   */
  private void objektEinlesen (Object objekt) throws IOException
  {
    if (EAExport.istListe (objekt))
    {
      /* Verarbeitung von iterierbaren Listen (z.B. ArrayList)          */
      if (objekt instanceof Iterable)
      {
        iterableVerarbeiten (objekt);
      }
      /* Verarbeitung von Schluessel-Wert-Listen (z.B. HashMap)         */
      if (objekt instanceof Map)
      {
        mapVerarbeiten (objekt);
      }
      /* Verarbeitung von Arrays                                        */
      if (objekt.getClass ().isArray ())
      {
        arrayVerarbeiten (objekt);
      }
    }
    else
    {
      datensatzListe.add (new Datensatz (objekt, this));
    }
    speichern ();
  }
  
  
  /**
   * Initialisiert das CSV-Dateiformat fuer den Export
   */
  private void initCSV ()
  {
    dateiBeginn = "";
    dateiEnde = "";
    
    datenBeginn = "";
    datenEnde = "";
    datensatzBeginn = "";
    datensatzEnde = "\n";
    feldBeginn = "";
    feldEnde = ";";
    
    kopfzeileBeginn = "";
    kopfzeileEnde = "\n";
    feldnameBeginn = "";
    feldnameEnde = ";";
  }
  
  
  /**
   * Initialisiert das TSV-Dateiformat fuer den Export
   */
  private void initTSV ()
  {
    dateiBeginn = "";
    dateiEnde = "";
    
    datenBeginn = "";
    datenEnde = "";
    datensatzBeginn = "";
    datensatzEnde = "\n";
    feldBeginn = "";
    feldEnde = "\t";
    
    kopfzeileBeginn = "";
    kopfzeileEnde = "\n";
    feldnameBeginn = "";
    feldnameEnde = "\t";
  }
  
  
  /**
   * Initialisiert das TXT-Dateiformat fuer den Export
   */
  private void initText ()
  {
    dateiBeginn = dateiname + "\n";
    dateiBeginn += "=== BOF ===\n";
    dateiEnde = "=== EOF ===\n";
    
    datenBeginn = "";
    datenEnde = "";
    datensatzBeginn = ":: Beginn Datensatz\n";
    datensatzEnde = ":: Ende Datensatz\n\n";
    feldBeginn = " > ";
    feldEnde = "\n";
    
    kopfzeileBeginn = "Verfuegbare Feldnamen: \n";
    kopfzeileEnde = "\n";
    feldnameBeginn = " * ";
    feldnameEnde = "\n";
  }
  
  
  /**
   * Initialisiert das HTML-Dateiformat fuer den Export
   */
  private void initHTML ()
  {
    dateiBeginn = "<html><body>\n ";
    dateiBeginn += "<h2>" + dateiname + "</h2>\n";
    dateiBeginn += "<table border=1>\n";
    dateiEnde = "</table>\n</body></html>\n";
    
    kopfzeileBeginn = "<tr>\n";
    kopfzeileEnde = "</tr>\n";
    feldnameBeginn = "\t<th>";
    feldnameEnde = "\t</th>\n";
    
    datenBeginn = "\n";
    datenEnde = "\n";
    datensatzBeginn = "<tr>\n";
    datensatzEnde = "</tr>\n";
    feldBeginn = "\t<td>";
    feldEnde = "\t</td>\n";
  }
  
  
  /**
   * Speichert die Datensatzliste im entsprechenden Dateiformat.
   */
  private void speichern () throws IOException
  {
    FileOutputStream fos = new FileOutputStream (
        ordnerpfad + "/" + dateiname);
    OutputStreamWriter osw = new OutputStreamWriter (fos);
    BufferedWriter out = new BufferedWriter (osw);
    
    out.write (dateiBeginn);
    
      out.write (kopfzeileBeginn);
      for (String datenfeldTitel : datensatzListe.get (0).
          gibDatenfeldListe ())         /* Erste Zeile mit Feldnamen    */
      {
        out.write (feldnameBeginn);
        out.write (datenfeldTitel);     /* Feldname                     */
        out.write (feldnameEnde);
      }
      out.write (kopfzeileEnde);
      
      out.write (datenBeginn);
      for (Datensatz ds : datensatzListe)
      {
        out.write (datensatzBeginn);

          for (Datenfeld df : ds.gibDatenfeldWertliste ())
          {
            out.write (feldBeginn);
            out.write (df.toString ()); /* Feldwert                     */
            out.write (feldEnde);
          }
        
        out.write (datensatzEnde);
      }
      out.write (datenEnde);
    
    out.write (dateiEnde);
    
    if (out != null)
    {
      out.close ();
    }
  }
  
  
  /**
   * Gibt das gewaehlte Exportformat zurueck
   */
  protected Exportformate gibExportformat ()
  {
    return exportformat;
  }
  
  
  /**
   * Gibt den Ordnerpfad zurueck
   */
  protected String gibOrdnerpfad ()
  {
    return ordnerpfad;
  }
  
  
  /**
   * Gibt den Dateinamen zurueck
   */
  protected String gibDateiname ()
  {
    return dateiname;
  }
  
  
  /**
   * Verarbeitet die Datensaetze einer Liste (Iterable)
   */
  private void iterableVerarbeiten (Object objekt)
  {
    Method method;
    Iterator<?> ergebnisliste;
    
    try
    {
      method = objekt.getClass ().getMethod ("iterator");
      ergebnisliste = (Iterator<?>) method.invoke (objekt);
      while (ergebnisliste.hasNext ())
      {
        datensatzListe.add (
            new Datensatz (ergebnisliste.next (), this));
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
  
  
  /**
   * Verarbeitet die Datensaetze einer Liste (Map)
   */
  private void mapVerarbeiten (Object objekt)
  {
    Method method;
    Set<?> ergebnis;
    Iterator<?> ergebnisliste;
    Map.Entry<?, ?> eintrag;
    
    try
    {
      method = objekt.getClass ().getMethod ("entrySet");
      ergebnis = (Set<?>) method.invoke (objekt);
      ergebnisliste = ergebnis.iterator ();
      while (ergebnisliste.hasNext ())  
      {
        eintrag = (Map.Entry<?, ?>) ergebnisliste.next ();
        datensatzListe.add (new Datensatz ("(" + 
            eintrag.getKey () + "|" + eintrag.getValue () + ")", this));
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
  
  
  /**
   * Verarbeitet die Datensaetze einer Liste (Array)
   */
  private void arrayVerarbeiten (Object objekt)
  {
    ArrayIterator ai = new ArrayIterator (objekt);
    
    try
    {
      while (ai.hasNext ())        
      {
        datensatzListe.add (new Datensatz (ai.next (), this));
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

}
